package token

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"gitee.com/jokces/kit/enum"
	"gitee.com/jokces/kit/errc"
	"io"
	"time"
)

// 采用的对称加密来计算得到对应的token

var (
	secretKey = "3e367a60ddc0699ea2f486717d5dcd174c4dee0bcf1855065ab74c348e550b78" //秘钥
	length    = 64
)

type Token struct {
	Id       int64  `json:"id"`       //用户的ID
	Exp      int64  `json:"exp"`      //过期的时间
	Typ      int32  `json:"typ"`      //1-admin,2-user
	ClientId string `json:"clientId"` //客户端的ID
}

func SwapToTyp(appType string) int32 {
	if "admin" == appType {
		return 1
	}
	if "api" == appType {
		return 2
	}
	return 0
}

func SwapToAppType(typ int32) string {
	if 1 == typ {
		return "admin"
	}
	if 2 == typ {
		return "api"
	}
	return ""
}

// SetBase 初始化的设置base
func SetBase(sk string) {
	if len(sk) != length {
		panic("secret key length is 64 bit")
	}
	secretKey = sk
}

// getSecretKey 初始化秘钥得到32位的数组
func getSecretKey() (*[32]byte, error) {
	key, err := hex.DecodeString(secretKey)
	if err != nil {
		return nil, err
	}
	return (*[32]byte)(key), nil
}

func newGCM(key *[32]byte) (gcm cipher.AEAD, err error) {
	block, err := aes.NewCipher(key[:])
	if err != nil {
		return
	}
	gcm, err = cipher.NewGCM(block)
	return
}

func randNonce(nonceSize int) []byte {
	nonce := make([]byte, nonceSize)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		panic(err)
	}
	return nonce
}

func encrypt(plaintext []byte, gcm cipher.AEAD) []byte {
	// 随机生成字节 slice，使得每次的加密结果具有随机性
	nonce := randNonce(gcm.NonceSize())
	// Seal 方法第一个参数 nonce，会把 nonce 本身加入到加密结果
	return gcm.Seal(nonce, nonce, plaintext, nil)
}

func decrypt(ciphertext []byte, gcm cipher.AEAD) ([]byte, error) {
	// 首先得到加密时使用的 nonce
	nonce := ciphertext[:gcm.NonceSize()]
	// 传入 nonce 并进行数据解密
	return gcm.Open(nil, nonce, ciphertext[gcm.NonceSize():], nil)
}

// GenerateToken 应该用 TokenBo 生成 Token
func GenerateToken(bo *Token) (string, error) {
	key, err := getSecretKey()
	if err != nil {
		return "", errc.ErrInternalErr.MultiMsg("load token error")
	}
	gcm, err := newGCM(key)
	if err != nil {
		return "", errc.ErrInternalErr.MultiMsg("load token error")
	}
	json := enum.ToJson(bo)
	bytes := make([]byte, len(json)+8)
	//设置过期时间
	binary.BigEndian.PutUint64(bytes, uint64(bo.Exp))
	copy(bytes[8:], []byte(json))
	return base64.RawURLEncoding.EncodeToString(encrypt(bytes, gcm)), nil
}

// ParseToken 解析 Token
// true: 在有效期内; false: 表示已过期
func ParseToken(token string) (*Token, bool, error) {
	out := &Token{}
	gcm, err := getSecretKey()
	if err != nil {
		return out, false, errc.ErrInternalErr.MultiMsg("load token error")
	}
	ng, err := newGCM(gcm)
	if err != nil {
		return out, false, errc.ErrInternalErr.MultiMsg("load token error")
	}

	tokenBytes, _ := base64.RawURLEncoding.DecodeString(token)
	bytes, err := decrypt(tokenBytes, ng)
	if err != nil {
		return out, false, errc.ErrInternalErr.MultiMsg("load token error")
	}

	genTime := binary.BigEndian.Uint64(bytes)
	if time.Unix(int64(genTime), 0).Unix() > time.Now().Unix() {
		boStr := string(bytes[8:])
		if err := enum.ToObject(boStr, out); err != nil {
			return out, false, errc.ErrInternalErr.MultiMsg("load token error")
		}
		return out, true, nil
	}
	return out, false, nil
}
